package com.kamistoat.meimeimall.meimeimallcart.service.impl;

import com.alibaba.fastjson.JSON;
import com.kamistoat.meimeimall.common.To.memberTo.LoginSuccessTo;
import com.kamistoat.meimeimall.common.To.orderTo.OrderItemTo;
import com.kamistoat.meimeimall.common.To.productTo.SkuCartInfoTo;
import com.kamistoat.meimeimall.common.To.wareTo.HasStockMapTo;
import com.kamistoat.meimeimall.common.To.wareTo.LockOrderItemWareTo;
import com.kamistoat.meimeimall.common.constant.AuthConstant;
import com.kamistoat.meimeimall.common.constant.CartConstant;
import com.kamistoat.meimeimall.common.constant.DatabaseConstant;
import com.kamistoat.meimeimall.common.dubboInterface.ProductDubboInterface;
import com.kamistoat.meimeimall.common.dubboInterface.WareDubboInterface;
import com.kamistoat.meimeimall.common.exception.BizCodeEnum;
import com.kamistoat.meimeimall.common.exception.RRException;
import com.kamistoat.meimeimall.common.threadlocal.ThreadLocalContextHolder;
import com.kamistoat.meimeimall.common.utils.R;
import com.kamistoat.meimeimall.meimeimallcart.dao.CartDao;
import com.kamistoat.meimeimall.meimeimallcart.entity.CartEntity;
import com.kamistoat.meimeimall.meimeimallcart.service.CartService;
import com.kamistoat.meimeimall.meimeimallcart.vo.CartItemVo;
import com.kamistoat.meimeimall.meimeimallcart.vo.CartVo;
import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service("cartService")
public class CartServiceImpl implements CartService {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    CartDao cartDao;

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    ThreadPoolExecutor threadPoolExecutor;

    @Reference
    ProductDubboInterface productDubboInterface;
    @Reference
    WareDubboInterface wareDubboInterface;

    /**
     * 获取<b>当前登录用户</b>购物车详细信息
     *
     * @return CartVo对象
     */
    @Override
    public CartVo getCart() {
        CartVo cartVo = new CartVo();

        /**
         * 获取缓存购物车中的商品信息。
         * 此处有两个重点:
         * 1. 如果缓存中不存在购物车，则先根据数据库的冷备份恢复
         * 2. 每次进入购物车，都需要查询所有商品的最新状态
         */
        List<OrderItemTo> cartItemToList = getCartItemToList();
        cartVo.setOrderItemTos(cartItemToList);
        return cartVo;
    }

    /**
     * 获取<b>当前登录用户</b>缓存购物车下的所有购物商品列表.
     * 1. 如果缓存中不存在购物车，则先根据数据库的冷备份恢复
     * 2. 每次进入购物车，都需要查询所有商品的最新状态
     *
     * @return 购物车商品列表
     */
    @Override
    public List<OrderItemTo> getCartItemToList() {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        return getCartItemToList(loginSuccessTo.getId());
    }

    /**
     * 获取<b>指定用户</b>缓存购物车下的所有购物商品列表.
     * 1. 如果缓存中不存在购物车，则先根据数据库的冷备份恢复
     * 2. 每次进入购物车，都需要查询所有商品的最新状态
     *
     * @param skuId 用户skuId
     * @return
     */
    @Override
    public List<OrderItemTo> getCartItemToList(Long skuId) {
        // 获取redisOpera，1.2两条要求通过该操作保证
        BoundHashOperations<String, Long, String> cartRedisOpera = getCartRedisOpera(skuId);
        // 获取这个reids下的所有购物商品value
        List<String> cartItemList = cartRedisOpera.values();
        if (cartItemList != null && cartItemList.size() > 0) {
            return cartItemList.stream().map((cartItem) -> JSON.parseObject(cartItem, OrderItemTo.class)).collect(Collectors.toList());
        } else {
            return new ArrayList<OrderItemTo>();
        }
    }

    /**
     * 绑定当前登录用户在缓存中的购物车。
     * 购物车核心，所有购物车操作都会先调用该方法。该方法必须完成如下两个方法:
     * 1. 如果缓存中存在，则需要刷新商品的最新状态。
     * 2. 如果缓存中不存在该购物车，则需要先从数据库中重构购物车到缓存。
     *
     * @return 对redis中指定哈希表绑定的redis操作类
     */
    public BoundHashOperations<String, Long, String> getCartRedisOpera() {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        return getCartRedisOpera(loginSuccessTo.getId());
    }

    /**
     * 绑定指定用户在缓存中的购物车。
     * 购物车核心，所有购物车操作都会先调用该方法。该方法必须完成如下两个方法:
     * 1. 如果缓存中存在，则需要刷新商品的最新状态。
     * 2. 如果缓存中不存在该购物车，则需要先从数据库中重构购物车到缓存。
     *
     * @return 对redis中指定哈希表绑定的redis操作类
     */
    public BoundHashOperations<String, Long, String> getCartRedisOpera(Long memberId) {
        // 缓存Key
        String cartKey = CartConstant.CART_PREFIX + memberId;
        Boolean hasKey = stringRedisTemplate.hasKey(cartKey);
        /**
         * 缓存中存在，则刷新商品的最新状态
         */
        if (hasKey != null && hasKey) {
            BoundHashOperations<String, Long, String> redisOpera = stringRedisTemplate.boundHashOps(cartKey);
            List<CartItemVo> cartItemVoList = redisOpera.values().stream().map((value) -> {
                OrderItemTo orderItemTo = JSON.parseObject(value, OrderItemTo.class);
                return new CartItemVo(orderItemTo.getSkuId(), orderItemTo.getCount(), orderItemTo.getCheck());
            }).collect(Collectors.toList());
            List<OrderItemTo> orderItemToList = constructItemTos(cartItemVoList);
            for (OrderItemTo orderItemTo : orderItemToList) {
                redisOpera.put(orderItemTo.getSkuId(), JSON.toJSONString(orderItemTo));
            }
            return redisOpera;
        }
        /**
         * 如果缓存中没有当前用户的购物车结构，则直接从数据库中查询之前同步的内容，重新构建最新商品.
         */
        else {
            CartEntity selectWrapper = new CartEntity();
            selectWrapper.setMemberId(memberId);
            selectWrapper.setDelete(DatabaseConstant.UN_LOGISTIC_DELETED);
            List<CartEntity> cartEntities = cartDao.selectByWrapper(selectWrapper);
            CartEntity selectOne = cartEntities.get(0);
            List<CartItemVo> cartItemVoList = JSON.parseArray(selectOne.getCartJsonString(), CartItemVo.class);
            List<OrderItemTo> orderItemToList = constructItemTos(cartItemVoList);
            BoundHashOperations<String, Long, String> redisOpera = stringRedisTemplate.boundHashOps(cartKey);
            for (OrderItemTo orderItemTo : orderItemToList) {
                redisOpera.put(orderItemTo.getSkuId(), JSON.toJSONString(orderItemTo));
            }
            return redisOpera;
        }
    }

    /**
     * 根据 CartItemVo 列表构建最新状态的 OrderItemTo.
     * 期间回去数据库查询最新的商品状态.
     */
    private List<OrderItemTo> constructItemTos(List<CartItemVo> cartItemVoList) {
        List<OrderItemTo> orderItemToList = new ArrayList<>();
        for (CartItemVo cartItemVo : cartItemVoList) {
            OrderItemTo orderItemTo = new OrderItemTo();
            orderItemTo.setSkuId(cartItemVo.getSkuId());
            orderItemTo.setCount(cartItemVo.getCount());
            orderItemTo.setCheck(cartItemVo.isSelected());
            /**
             * 1. RPC异步查询商品基础信息
             */
            CompletableFuture<Void> getSkuCartInfoFuture = CompletableFuture.runAsync(() -> {
                // Dubbo调用
                R skuBizInfoR = productDubboInterface.getSkuBizInfoForCart(cartItemVo.getSkuId());
                // 查询成功则更新，查询失败则暂时复用旧的数据
                if (skuBizInfoR.getCode() == 0) {
                    SkuCartInfoTo skuCartInfoTo = JSON.parseObject((String) skuBizInfoR.get("skuCartInfoTo"), SkuCartInfoTo.class);
                    // 2 将商品添加到购物车
                    orderItemTo.setImage(skuCartInfoTo.getSkuDefaultImg());
                    orderItemTo.setTitle(skuCartInfoTo.getSkuTitle());
                    orderItemTo.setPrice(skuCartInfoTo.getPrice());
                    orderItemTo.setBrandId(skuCartInfoTo.getBrandId());
                    orderItemTo.setBrandName(skuCartInfoTo.getBrandName());
                } else {
                    logger.error(cartItemVo.getSkuId() + "商品更新购物车商品状态失败，复用旧数据。错误:" + skuBizInfoR.getMsg());
                    throw new RRException(BizCodeEnum.REFRESH_ITEM_ERROR.getMessage(), BizCodeEnum.REFRESH_ITEM_ERROR.getCode());
                }
            }, threadPoolExecutor);

            /**
             * 2. RPC异步查询是否有库存
             */
            CompletableFuture<Void> hasStockFuture = CompletableFuture.runAsync(() -> {
                R hasStockR = wareDubboInterface.hasStock(Collections.singletonList(cartItemVo.getSkuId()));
                if (hasStockR.getCode() == 0) {
                    HasStockMapTo hasStockMapTo = JSON.parseObject((String) hasStockR.get("hasStockMapTo"), HasStockMapTo.class);
                    orderItemTo.setHasStock(hasStockMapTo.getHasStockMap().get(cartItemVo.getSkuId()));
                } else {
                    logger.error(cartItemVo.getSkuId() + "商品更新购物车商品状态失败，复用旧数据。错误:" + hasStockR.getMsg());
                    throw new RRException(BizCodeEnum.REFRESH_ITEM_ERROR.getMessage(), BizCodeEnum.REFRESH_ITEM_ERROR.getCode());
                }
            }, threadPoolExecutor);

            /**
             * 3. 根据skuId获取String形式的销售属性
             */
            CompletableFuture<Void> getStringAttrFuture = CompletableFuture.runAsync(() -> {
                // Dubbo调用
                R stringAttrR = productDubboInterface.getStringAttr(cartItemVo.getSkuId());
                if (stringAttrR.getCode() == 0) {
                    List<String> stringAttr = JSON.parseArray((String) stringAttrR.get("stringAttrList"), String.class);
                    orderItemTo.setSkuAttr(stringAttr);
                } else {
                    logger.error(cartItemVo.getSkuId() + "商品更新购物车商品状态失败，复用旧数据。错误:" + stringAttrR.getMsg());
                    throw new RRException(BizCodeEnum.REFRESH_ITEM_ERROR.getMessage(), BizCodeEnum.REFRESH_ITEM_ERROR.getCode());
                }
            }, threadPoolExecutor);

            // 等待异步查询全部完成
            try {
                CompletableFuture.allOf(getSkuCartInfoFuture, hasStockFuture, getStringAttrFuture).get();
                orderItemToList.add(orderItemTo);
            } catch (Exception exception) {
                logger.error(cartItemVo.getSkuId() + "商品更新购物车商品状态失败，复用旧数据");
            }
        }
        return orderItemToList;
    }

    /**
     * 将一件商品添加到缓存中的购物车结构体中。
     *
     * @param skuId 商品skuId
     * @param num   购买数量
     * @return
     */
    @Override
    public void addToCart(Long skuId, Integer num) {
        BoundHashOperations<String, Long, String> cartKeyRedis = getCartRedisOpera();
        addToCartWithRedisOpera(skuId, num, cartKeyRedis);
    }

    /**
     * 将一件商品添加到缓存中的购物车结构体中。携带指定的redisOpera
     */
    private void addToCartWithRedisOpera(Long skuId, Integer num, BoundHashOperations<String, Long, String> cartKeyRedis) {
        /**
         * 如果购物车中已经有这个商品了，直接增加数量
         */
        String item = cartKeyRedis.get(skuId);// 直接在当前购物车里用键名去查
        if (!StringUtils.isEmpty(item)) {
            // 购物车中已经有这个商品
            // 先将原始商品信息提取出来
            OrderItemTo orderItemTo = JSON.parseObject(item, OrderItemTo.class);
            // 将商品原来的数量加上新的数量后再放回reids中
            orderItemTo.setCount(orderItemTo.getCount() + num);
            cartKeyRedis.put(skuId, JSON.toJSONString(orderItemTo));
            // 当然在页面上展示的还是这一次的num
            orderItemTo.setCount(num);
        }
        /**
         * 向购物车中添加新商品
         */
        else {
            List<OrderItemTo> orderItemToList = constructItemTos(Collections.singletonList(new CartItemVo(skuId, num, true)));
            cartKeyRedis.put(skuId, JSON.toJSONString(orderItemToList.get(0)));
        }
    }


    /**
     * 查询刚刚成功添加的商品
     *
     * @param skuId
     * @param num
     * @return
     */
    @Override
    public OrderItemTo getCartItem(Long skuId, Integer num) {
        BoundHashOperations<String, Long, String> cartKeyRedis = getCartRedisOpera();
        String item = cartKeyRedis.get(skuId);// 直接在当前购物车里用键名去查
        // 就不用判断是否为空了
        // 将刚刚添加的商品信息提取出来
        OrderItemTo orderItemTo = JSON.parseObject(item, OrderItemTo.class);
        // 将商品的数量更改为刚刚添加的数据
        orderItemTo.setCount(num);
        // 返回给页面展示
        return orderItemTo;

    }


    /**
     * 勾选购物车商品项
     *
     * @param skuId
     * @param check
     */
    @Override
    public void checkItem(Long skuId, Integer check) {
        // 首先把绑定的redis购物车获取来。此时无需判断是离线购物车还是在线购物车。
        BoundHashOperations<String, Long, String> cartKeyRedis = getCartRedisOpera();
        // 从当前绑定redis购物车中以skuId为键值将对应的商品项拿出来
        OrderItemTo orderItemTo = JSON.parseObject(cartKeyRedis.get(skuId), OrderItemTo.class);
        // 更改商品项的check状态
        if (orderItemTo != null) {
            orderItemTo.setCheck(check == 1);
        }
        // 还得把修改完的购物商品信息保存回redis中，依然是值和value都得转成String才行
        String orderItemToJSON = JSON.toJSONString(orderItemTo);
        cartKeyRedis.put(skuId, orderItemToJSON);
    }

    /**
     * 修改购物商品项数量
     *
     * @param skuId
     * @param num
     */
    @Override
    public void countItem(Long skuId, Integer num) {
        // 首先把绑定的redis购物车获取来。此时无需判断是离线购物车还是在线购物车。
        BoundHashOperations<String, Long, String> cartKeyRedis = getCartRedisOpera();
        // 从当前绑定redis购物车中以skuId为键值将对应的商品项拿出来
        OrderItemTo orderItemTo = JSON.parseObject(cartKeyRedis.get(skuId), OrderItemTo.class);
        // 更改商品项数量
        if (orderItemTo != null) {
            orderItemTo.setCount(num);
        }
        // 还得把修改完的购物商品信息保存回redis中，依然是值和value都得转成String才行
        String orderItemToJSON = JSON.toJSONString(orderItemTo);
        cartKeyRedis.put(skuId, orderItemToJSON);
    }

    /**
     * 删除某个商品项
     *
     * @param skuId
     */
    @Override
    public void deleteItem(Long skuId) {
        // 首先把绑定的redis购物车获取来。此时无需判断是离线购物车还是在线购物车。
        BoundHashOperations<String, Long, String> cartKeyRedis = getCartRedisOpera();
        cartKeyRedis.delete(skuId.toString());
    }

    /**
     * 获取当前用户购物车中所有被选中的商品
     * 并不需要传入用户Id，因为购物车服务同样有拦截器的ThreadLocal，直接从里面拿就可以了
     */
    @Override
    public List<OrderItemTo> getOrderCartChecked() {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        List<OrderItemTo> orderItemToList = getCartItemToList(loginSuccessTo.getId());
        // 假如购物车为空，直接返回
        if (orderItemToList == null || orderItemToList.size() == 0) {
            return null;
        } else {
            List<OrderItemTo> orderItemToCheckedList = orderItemToList.stream()
                    .filter(orderItemTo -> {
                        // 只获取已经选中的商品
                        return orderItemTo.getCheck();
                    }).collect(Collectors.toList());
            return orderItemToCheckedList;
        }

    }

    /**
     * 从购车中删除当前订单中的商品
     *
     * @param lockOrderItemWareToList
     */
    @Override
    public void deleteOrderItemInCart(List<LockOrderItemWareTo> lockOrderItemWareToList) {
        LoginSuccessTo loginSuccessTo = JSON.parseObject((String) ThreadLocalContextHolder.get(AuthConstant.LOGIN_USER_THREADLOCAL_NAME), LoginSuccessTo.class);
        BoundHashOperations<String, String, String> cartHashBound =
                stringRedisTemplate.boundHashOps(CartConstant.CART_PREFIX + loginSuccessTo.getId());
        Object[] skuArrays = lockOrderItemWareToList.stream().map(lockOrderItemWareTo -> {
            return lockOrderItemWareTo.getSkuId().toString();
        }).toArray();
        cartHashBound.delete(skuArrays);
    }

    /**
     * 从购车中删除当前订单中的商品. 回滚动作
     *
     * @param lockOrderItemWareToList
     */
    @Override
    public void deleteOrderItemInCartRollback(List<LockOrderItemWareTo> lockOrderItemWareToList) {

    }

    /**
     * 用于删除指定的cartKey购物车下的所有购物商品列表
     *
     * @param cartKey 购物车的key
     */
    @Override
    public void clearCart(String cartKey) {
        // 直接将整个键下的购物车删除
        stringRedisTemplate.delete(CartConstant.CART_PREFIX + cartKey);
    }

}
